﻿#region "Copyright (C) Lenny Granovsky. 2011-2013"
//    This library provides extended cache capabilities to the ASP.NET applications.
//
//                Copyright (C) Lenny Granovsky. 2011-2013. 
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 2 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//    This program comes with ABSOLUTELY NO WARRANTY.
//    This is free software, and you are welcome to redistribute it
//    under certain conditions;
#endregion

using System;
using System.Web;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Xml;
using System.Configuration;
using System.Text.RegularExpressions;

namespace FlexiCache
{
    [Serializable()]
    public class ExternalCacheConfigHandler : IConfigurationSectionHandler
    {
        public ExternalCacheConfigHandler()
        {
        }

        public object Create(object parent, object configContext, System.Xml.XmlNode section)
        {
            XmlElement root = (XmlElement)section;
            return new CachingConfiguration(root);
        }
    }

    /// <summary>
    /// This class holds settings for site caching functionality.
    /// </summary>
    [Serializable()]
    public class CachingConfiguration
    {
        private const int MetaDataSize = 0x1000; //4Kb
        public const int MaxRecordSize = 0x400000; //4Mb
        public const int MaxDataSizeLength = MaxRecordSize - MetaDataSize;
        /// <summary>
        /// Gets the max length of metadata field allowed to store in the cache storage.
        /// </summary>
        public const int MaxMetadataSize = 512; //chars
        /// <summary>
        /// The interval used by Session on Demand renew process to renew the expiration interval in minutes. 
        /// There is no need to renew data every request, so we would do so every interval.
        /// </summary>
        public const int SessionOnDemandRenewFlexibilityInterval = 2;
        /// <summary>
        /// Gets out-of-process Cache On Demand configuration.
        /// </summary>
        public CacheStorageConfigInfo CacheOnDemandConfig { get; protected set; }
        /// <summary>
        /// Gets out-of-process Session On Demand configuration.
        /// </summary>
        public CacheStorageConfigInfo SessionOnDemandConfig { get; protected set; }
        /// <summary>
        /// Gets custom output caching configuration.
        /// </summary>
        public CacheStorageConfigInfo CustomOutputCacheConfig { get; protected set; }
        /// <summary>
        /// Gets value used to periodically run cache cleanup process. This value is in seconds.
        /// </summary>
        public int CacheCleanupInterval { get; protected set; }
        public bool IsCacheCleanupEnabled
        {
            get
            {
                return this.CacheCleanupInterval > 0
                    && (this.CustomOutputCacheConfig.IsEnabled || this.CacheOnDemandConfig.IsEnabled || this.SessionOnDemandConfig.IsEnabled);
            }
        }
        /// <summary>
        /// Gets a boolean value indicated if error logging is enabled.
        /// </summary>
        public bool EnableErrorLogging { get; protected set; }
        /// <summary>
        /// Gets boolean value indicating whether meta tags should be automatically collected when the cache is saved.
        /// </summary>
        public bool EnableMetatagsCollection { get; protected set; }

        /// <summary>
        /// Gets current caching configuration settings.
        /// </summary>
        public static CachingConfiguration Current
        {
            get
            {
                return (CachingConfiguration)ConfigurationManager.GetSection("externalCacheConfig");
            }
        }

        internal CachingConfiguration(XmlElement xmlData)
        {
            if (xmlData == null)
                throw new ArgumentNullException("Xml configuration element <externalCacheConfig> cannot be null.");

            XmlNode oNode = xmlData.Attributes.GetNamedItem("cacheCleanupInterval");
            if (oNode == null)
                throw new ApplicationException("Cannot find an <@cacheCleanupInterval> attribute node in externalCacheConfig xml.");
            this.CacheCleanupInterval = int.Parse(oNode.InnerText.Trim());
            oNode = xmlData.Attributes.GetNamedItem("enableMetatags");
            if (oNode != null)
                this.EnableMetatagsCollection = bool.Parse(oNode.InnerText.Trim());

            oNode = xmlData.Attributes.GetNamedItem("enableErrorLogging");
            if (oNode != null)
                this.EnableErrorLogging = bool.Parse(oNode.InnerText.Trim());
            else this.EnableErrorLogging = true;

            XmlNode subNode = xmlData.SelectSingleNode("customOutputCache");
            if (subNode == null)
                throw new ApplicationException("Cannot find an <customOutputCache> node in externalCacheConfig xml.");
            this.CustomOutputCacheConfig = new CacheStorageConfigInfo()
            {
                IsAcknowledgedWrite = false,
                MaxDataSizeLength = CachingConfiguration.MaxDataSizeLength,
                UseCompression = false,
                TracePrefix = "CustomOutputCache"
            };

            oNode = subNode.Attributes.GetNamedItem("enabled");
            if (oNode == null)
                throw new ApplicationException("Cannot find an <customOutputCache/@enabled> attribute node in externalCacheConfig xml.");
            this.CustomOutputCacheConfig.IsEnabled = bool.Parse(oNode.InnerText.Trim().ToLower());
            if (this.CustomOutputCacheConfig.IsEnabled)
            {
                oNode = subNode.Attributes.GetNamedItem("dbConnectionStringName");
                if (oNode == null)
                    throw new ApplicationException("Cannot find an <customOutputCache/@dbConnectionStringName> attribute node in externalCacheConfig xml.");
                this.CustomOutputCacheConfig.DBConnectionString = ConfigurationManager.ConnectionStrings[oNode.InnerText.Trim()].ConnectionString;
                oNode = subNode.Attributes.GetNamedItem("dbName");
                if (oNode == null)
                    throw new ApplicationException("Cannot find an <customOutputCache/@dbName> attribute node in externalCacheConfig xml.");
                this.CustomOutputCacheConfig.CacheDatabaseName = oNode.InnerText.Trim();
                oNode = subNode.Attributes.GetNamedItem("adapterType");
                if (oNode == null)
                    throw new ApplicationException("Cannot find an <customOutputCache/@adapterType> attribute node in externalCacheConfig xml.");
                this.CustomOutputCacheConfig.AdapterType = Type.GetType(oNode.InnerText.Trim(), true);
            }

            subNode = xmlData.SelectSingleNode("cacheOnDemand");
            if (oNode == null)
                throw new ApplicationException("Cannot find an <cacheOnDemand> node in externalCacheConfig xml.");
            this.CacheOnDemandConfig = new CacheStorageConfigInfo()
            {
                IsAcknowledgedWrite = true,
                MaxDataSizeLength = 0,
                UseCompression = true,
                TracePrefix = "CacheOnDemand"
            };

            oNode = subNode.Attributes.GetNamedItem("enabled");
            if (oNode == null)
                throw new ApplicationException("Cannot find an <cacheOnDemand/@enabled> attribute node in externalCacheConfig xml.");
            this.CacheOnDemandConfig.IsEnabled = bool.Parse(oNode.InnerText.Trim().ToLower());
            if (this.CacheOnDemandConfig.IsEnabled)
            {
                oNode = subNode.Attributes.GetNamedItem("dbConnectionStringName");
                if (oNode == null)
                    throw new ApplicationException("Cannot find an <cacheOnDemand/@dbConnectionStringName> attribute node in externalCacheConfig xml.");
                this.CacheOnDemandConfig.DBConnectionString = ConfigurationManager.ConnectionStrings[oNode.InnerText.Trim()].ConnectionString;
                oNode = subNode.Attributes.GetNamedItem("dbName");
                if (oNode == null)
                    throw new ApplicationException("Cannot find an <cacheOnDemand/@dbName> attribute node in externalCacheConfig xml.");
                this.CacheOnDemandConfig.CacheDatabaseName = oNode.InnerText.Trim();
                oNode = subNode.Attributes.GetNamedItem("adapterType");
                if (oNode == null)
                    throw new ApplicationException("Cannot find an <cacheOnDemand/@adapterType> attribute node in externalCacheConfig xml.");
                this.CacheOnDemandConfig.AdapterType = Type.GetType(oNode.InnerText.Trim(), true);
            }

            subNode = xmlData.SelectSingleNode("sessionOnDemand");
            if (oNode == null)
                throw new ApplicationException("Cannot find an <sessionOnDemand> node in externalCacheConfig xml.");
            this.SessionOnDemandConfig = new CacheStorageConfigInfo()
            {
                IsAcknowledgedWrite = true,
                MaxDataSizeLength = 0,
                UseCompression = true,
                TracePrefix = "SessionOnDemand"
            };

            oNode = subNode.Attributes.GetNamedItem("enabled");
            if (oNode == null)
                throw new ApplicationException("Cannot find an <sessionOnDemand/@enabled> attribute node in externalCacheConfig xml.");
            this.SessionOnDemandConfig.IsEnabled = bool.Parse(oNode.InnerText.Trim().ToLower());
            if (this.SessionOnDemandConfig.IsEnabled)
            {
                oNode = subNode.Attributes.GetNamedItem("dbConnectionStringName");
                if (oNode == null)
                    throw new ApplicationException("Cannot find an <sessionOnDemand/@dbConnectionStringName> attribute node in externalCacheConfig xml.");
                this.SessionOnDemandConfig.DBConnectionString = ConfigurationManager.ConnectionStrings[oNode.InnerText.Trim()].ConnectionString;
                oNode = subNode.Attributes.GetNamedItem("dbName");
                if (oNode == null)
                    throw new ApplicationException("Cannot find an <sessionOnDemand/@dbName> attribute node in externalCacheConfig xml.");
                this.SessionOnDemandConfig.CacheDatabaseName = oNode.InnerText.Trim();
                oNode = subNode.Attributes.GetNamedItem("adapterType");
                if (oNode == null)
                    throw new ApplicationException("Cannot find an <sessionOnDemand/@adapterType> attribute node in externalCacheConfig xml.");
                this.SessionOnDemandConfig.AdapterType = Type.GetType(oNode.InnerText.Trim(), true);
            }
        }
    }
}
